/*
 * signal.c
 *
 * Copyright (C) 2010-2020 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/
 */

#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "cf_thread.h"
#include "log.h"


//==========================================================
// Typedefs & constants.
//

typedef void (*action_t)(int sig, siginfo_t *info, void *ctx);

// String constants in version.c, generated by make.
extern const char aerospike_build_type[];
extern const char aerospike_build_id[];
extern const char aerospike_build_os[];


//==========================================================
// Globals.
//

// The mutex that the main function deadlocks on after starting the service.
extern pthread_mutex_t g_main_deadlock;
extern bool g_startup_complete;


//==========================================================
// Local helpers.
//

static inline void
set_action(int sig_num, action_t act)
{
	struct sigaction sa;
	memset(&sa, 0, sizeof(sa));

	sa.sa_sigaction = act;
	sigemptyset(&sa.sa_mask);
	// SA_SIGINFO prefers sa_sigaction over sa_handler.
	sa.sa_flags = SA_RESTART | SA_SIGINFO;

	if (sigaction(sig_num, &sa, NULL) < 0) {
		cf_crash(AS_AS, "could not register signal handler for %d", sig_num);
	}
}

static inline void
set_handler(int sig_num, sighandler_t hand)
{
	struct sigaction sa;
	memset(&sa, 0, sizeof(sa));

	sa.sa_handler = hand;
	sigemptyset(&sa.sa_mask);
	// No SA_SIGINFO; use sa_handler.
	sa.sa_flags = SA_RESTART;

	if (sigaction(sig_num, &sa, NULL) < 0) {
		cf_crash(AS_AS, "could not register signal handler for %d", sig_num);
	}
}

static inline void
reraise_signal(int sig_num)
{
	if (getpid() == 1) {
		cf_warning(AS_AS, "pid 1 received signal %d - exiting", sig_num);
		_exit(1);
	}

	set_handler(sig_num, SIG_DFL);
	raise(sig_num);
}


//==========================================================
// Signal handlers.
//

// We get here on some crashes.
void
as_sig_handle_abort(int sig_num, siginfo_t *info, void *ctx)
{
	cf_warning(AS_AS, "SIGABRT received, aborting %s build %s os %s",
			aerospike_build_type, aerospike_build_id, aerospike_build_os);

	cf_log_stack_trace(ctx);
	reraise_signal(sig_num);
}

void
as_sig_handle_bus(int sig_num, siginfo_t *info, void *ctx)
{
	cf_warning(AS_AS, "SIGBUS received, aborting %s build %s os %s",
			aerospike_build_type, aerospike_build_id, aerospike_build_os);

	cf_log_stack_trace(ctx);
	reraise_signal(sig_num);
}

// Floating point exception.
void
as_sig_handle_fpe(int sig_num, siginfo_t *info, void *ctx)
{
	cf_warning(AS_AS, "SIGFPE received, aborting %s build %s os %s",
			aerospike_build_type, aerospike_build_id, aerospike_build_os);

	cf_log_stack_trace(ctx);
	reraise_signal(sig_num);
}

// This signal is our cue to roll the log.
void
as_sig_handle_hup(int sig_num, siginfo_t *info, void *ctx)
{
	cf_info(AS_AS, "SIGHUP received, rolling log");

	cf_log_rotate();
}

// We get here on some crashes.
void
as_sig_handle_ill(int sig_num, siginfo_t *info, void *ctx)
{
	cf_warning(AS_AS, "SIGILL received, aborting %s build %s os %s",
			aerospike_build_type, aerospike_build_id, aerospike_build_os);

	cf_log_stack_trace(ctx);
	reraise_signal(sig_num);
}

// We get here on cf_crash_nostack(), cf_assert_nostack(), or Ctrl-C when
// running in foreground.
void
as_sig_handle_int(int sig_num, siginfo_t *info, void *ctx)
{
	cf_warning(AS_AS, "SIGINT received, shutting down %s build %s os %s",
			aerospike_build_type, aerospike_build_id, aerospike_build_os);

	if (! g_startup_complete) {
		cf_warning(AS_AS, "startup was not complete, exiting immediately");
		_exit(1);
	}

	pthread_mutex_unlock(&g_main_deadlock);
}

// We get here if we intentionally trigger the signal.
void
as_sig_handle_quit(int sig_num, siginfo_t *info, void *ctx)
{
	cf_warning(AS_AS, "SIGQUIT received, aborting %s build %s os %s",
			aerospike_build_type, aerospike_build_id, aerospike_build_os);

	cf_log_stack_trace(ctx);
	reraise_signal(sig_num);
}

// We get here on some crashes.
void
as_sig_handle_segv(int sig_num, siginfo_t *info, void *ctx)
{
	cf_warning(AS_AS, "SIGSEGV received, aborting %s build %s os %s",
			aerospike_build_type, aerospike_build_id, aerospike_build_os);

	cf_log_stack_trace(ctx);
	reraise_signal(sig_num);
}

// We get here on normal shutdown.
void
as_sig_handle_term(int sig_num, siginfo_t *info, void *ctx)
{
	cf_info(AS_AS, "SIGTERM received, shutting down %s build %s os %s",
			aerospike_build_type, aerospike_build_id, aerospike_build_os);

	if (! g_startup_complete) {
		cf_warning(AS_AS, "startup was not complete, exiting immediately");
		_exit(0);
	}

	pthread_mutex_unlock(&g_main_deadlock);
}

// We get here on cf_crash() and cf_assert().
void
as_sig_handle_usr1(int sig_num, siginfo_t *info, void *ctx)
{
	cf_warning(AS_AS, "SIGUSR1 received, aborting %s build %s os %s",
			aerospike_build_type, aerospike_build_id, aerospike_build_os);

	cf_log_stack_trace(ctx);
	reraise_signal(SIGABRT);
}


//==========================================================
// Public API.
//

void
as_signal_setup()
{
	set_action(SIGABRT, as_sig_handle_abort);
	set_action(SIGBUS, as_sig_handle_bus);
	set_action(SIGFPE, as_sig_handle_fpe);
	set_action(SIGHUP, as_sig_handle_hup);
	set_action(SIGILL, as_sig_handle_ill);
	set_action(SIGINT, as_sig_handle_int);
	set_action(SIGQUIT, as_sig_handle_quit);
	set_action(SIGSEGV, as_sig_handle_segv);
	set_action(SIGTERM, as_sig_handle_term);
	set_action(SIGUSR1, as_sig_handle_usr1);
	set_action(SIGUSR2, cf_thread_traces_action);

	// Block SIGPIPE signal when there is some error while writing to pipe. The
	// write() call will return with a normal error which we can handle.
	set_handler(SIGPIPE, SIG_IGN);
}
